﻿namespace Home.Services
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Web.OData;
    using System.Web.OData.Extensions;
    using System.Web.OData.Formatter;
    using System.Web.OData.Formatter.Serialization;
    using System.Web.OData.Routing;
    using Microsoft.OData.Edm;

    // TODO: delete the class while OData fix the following issue
    // ETagMessageHandler does not support contained entity set (contained collection navigation property) in the current version of OData Web API. We write our self one.
    public class ODataETagMessageHandler : ETagMessageHandler
    {
        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            Debug.Assert(request != null);

            var configuration = request.GetConfiguration();
            var response = await base.SendAsync(request, cancellationToken);
            var result = default(HttpResponseMessage);
            if (response == null || !response.IsSuccessStatusCode || response.StatusCode == HttpStatusCode.NoContent)
            {
                result = response;
            }
            else
            {
                var path = request.ODataProperties().Path;
                var model = request.ODataProperties().Model;
                var type = GetSingleEntityEntityType(path);
                var instance = GetSingleEntityObject(response);
                var reference = GetTypeReference(model, type, instance);
                if (reference != null)
                {
                    var context = CreateInstanceContext(reference, instance);
                    var handler = configuration.GetETagHandler();
                    var etag = CreateETag(context, handler);
                    if (etag != null)
                    {
                        response.Headers.ETag = etag;
                    }
                }

                result = response;
            }

            return result;
        }

        private static IEdmEntityTypeReference GetTypeReference(IEdmModel model, IEdmEntityType edmType, object value)
        {
            if (model == null || edmType == null || value == null)
            {
                return null;
            }

            IEdmTypeReference edmTypeReference = model.GetEdmTypeReference(value.GetType());
            if (edmTypeReference != null && edmTypeReference.Definition.IsOrInheritsFrom(edmType))
            {
                return (IEdmEntityTypeReference)edmTypeReference;
            }

            return null;
        }

        private static EntityTagHeaderValue CreateETag(EntityInstanceContext entityInstanceContext, IETagHandler handler)
        {
            var enumerable = from c in entityInstanceContext.EntityType.GetConcurrencyProperties()
                             orderby c.Name
                             select c;
            var dictionary = new Dictionary<string, object>();
            foreach (var current in enumerable)
            {
                dictionary.Add(current.Name, entityInstanceContext.GetPropertyValue(current.Name));
            }

            return handler.CreateETag(dictionary);
        }

        private static object GetSingleEntityObject(HttpResponseMessage response)
        {
            var content = response.Content as ObjectContent;
            return content != null ? content.Value : null;
        }

        private static EntityInstanceContext CreateInstanceContext(IEdmEntityTypeReference reference, object value)
        {
            var context = new ODataSerializerContext();
            return new EntityInstanceContext(context, reference, value);
        }

        internal static IEdmEntityType GetSingleEntityEntityType(ODataPath path)
        {
            if (path == null || path.Segments.Count == 0)
            {
                return null;
            }

            var num = path.Segments.Count - 1;
            while (num >= 0 && path.Segments[num].SegmentKind == "cast")
            {
                num--;
            }
            if (num < 0)
            {
                return null;
            }

            var segment = path.Segments[num];
            var kind = segment.SegmentKind;
            if (kind != null)
            {
                if (kind == "singleton" || kind == "key")
                {
                    return (IEdmEntityType)path.EdmType;
                }

                if (kind == "navigation")
                {
                    var navigation = (NavigationPathSegment)segment;
                    if (navigation.NavigationProperty.TargetMultiplicity() == EdmMultiplicity.ZeroOrOne || navigation.NavigationProperty.TargetMultiplicity() == EdmMultiplicity.One)
                    {
                        return (IEdmEntityType)path.EdmType;
                    }
                }
            }

            return null;
        }
    }
}
